iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Software Development

iOS 學習筆記系列 第 13

Day13 SwiftUI 06 - WebView

  • 分享至 

  • xImage
  •  

接下來我們來看看在SwiftUI 怎麼使用WebView 網頁的元件,SwiftUI 框架 有一個缺點,就是它未能提供所有 UIKit 有的 UI 控件,例如說由於 SwiftUI 還沒有定義 WebView,所以Apple 有一個 UIViewRepresentable 協定,讓你可以輕鬆打包 (wrap) 一個 UIView,並讓 SwiftUI 專案使用,以下我們就將傳統的 UI 元件包裝成 SwiftUI view,並使用 WKWebView 來加載 Web 應用程序,讓我們照著以下步驟來完成

  1. 在 SwiftUI 中使用 UIKit 中的任何組件都需要用 UIViewRepresentable 包裝

    首先我們先新增一個 Swift File:並命名為 WebView.swift,裡面內容為struct WebView,並實作UIViewRepresentable協定

    import SwiftUI
    import WebKit
    
    struct SwiftUIWebView: UIViewRepresentable {
    
    }
    

    實作 UIViewRepresentable 表示此 struct 定義的WebView,是有著 UIView 元件特性的 SwiftUI view,所以它可以包裝 UICollectionView,WKWebView,MKMapView 等 UI 元件,待會我們將用它包裝 WKWebView

  2. 要符合 UIViewRepresentable 協定,需實作兩個方法,makeUIView(context:) 以及updateUIView(_:context:)

    在程式區塊上建立makeUIView(context:) 方法,可經由Xcode 能自動完成

    https://ithelp.ithome.com.tw/upload/images/20210928/20118479rMsO2VqEVj.png

    再把makeUIView方法裡回傳的型別改成我們要用的WKWebView

     func makeUIView(context: Context) -> WKWebView {
     }
    

    同樣的方式,建立updateUIView(_:context:)方法,記得把參數uiView改成我們要用的WKWebView

    func updateUIView(_ uiView: WKWebView, context: Context) {
    }
    

    現在我們來完成以上這兩個方法的實作,首先makeUIView裡我們要回傳 WKWebView 的物件,這邊可以透過WKWebpagePreferences以及WKWebViewConfiguration來對我們的WebView 設置一些偏好設定

    範例:

      func makeUIView(context: Context) -> WKWebView {
            let prefs = WKWebpagePreferences()
            prefs.allowsContentJavaScript = false
            let config = WKWebViewConfiguration()
            config.defaultWebpagePreferences = prefs
            return WKWebView(
                frame: .zero,
                configuration: config
            )
        }
    

    接下來是updateUIView,它將在 SwiftUI view 畫面更新時被呼叫,我們可以在 WebView 裡宣告一個屬性來由外部傳入網址,然後在生成 WebView 時傳入內容,在 updateUIView 才載入網頁

    如下:

    struct WebView: UIViewRepresentable {
    
        let url: URL?
    
        func makeUIView(context: Context) -> WKWebView {
            let prefs = WKWebpagePreferences()
            prefs.allowsContentJavaScript = false
            let config = WKWebViewConfiguration()
            config.defaultWebpagePreferences = prefs
            return WKWebView(
                frame: .zero,
                configuration: config
            )
        }
    
        func updateUIView(_ uiView: WKWebView, context: Context) {
            guard let myURL = url else {
                return
            }
            let  request = URLRequest(url: myURL)
            uiView.load(request)
        }
    }
    

    接下來我們就可以使用我們的WebView 來載入我們要的網頁了

    struct ContentView: View {
        var body: some View {
            WebView(url: URL(string: "https://www.youtube.com/"))
        }
    }
    

目前我們只討論了 UIViewRepresentable 協定中的幾種方法。如果你需要在 UIKit 中使用委託 (delegate) 並與 SwiftUI 溝通,就必須實現 makeCoordinator 方法,並提供一個 Coordinator 實例。Coordinator 是 UIView 的委託 和 SwiftUI 之間的橋樑

我們在WebView struct 中,創建一個 Coordinator 類別並採用WKNavigationDelegate協定來處理webView 的導航事件,並在 makeCoordinator 方法回傳其實例,可搭配數據流等來做資料的傳遞

範例:

struct WebView: UIViewRepresentable {
    @Binding var title: String
    var url: URL
    var loadStatusChanged: ((Bool, Error?) -> Void)? = nil

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        view.navigationDelegate = context.coordinator
        view.load(URLRequest(url: url))
        return view
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // you can access environment via context.environment here
        // Note that this method will be called A LOT
    }

    func onLoadStatusChanged(perform: ((Bool, Error?) -> Void)?) -> some View {
        var copy = self
        copy.loadStatusChanged = perform
        return copy
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        let parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
            parent.loadStatusChanged?(true, nil)
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            parent.title = webView.title ?? ""
            parent.loadStatusChanged?(false, nil)
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            parent.loadStatusChanged?(false, error)
        }
    }
}
struct ContentView: View {
    @State var title: String = ""
    @State var error: Error? = nil
    
    var body: some View {
        WebView(title: $title, url: URL(string: "https://www.apple.com/")!)
                       .onLoadStatusChanged { loading, error in
                           if loading {
                               print("Loading started")
                               self.title = "Loading…"
                           }
                           else {
                               print("Done loading.")
                               if let error = error {
                                   self.error = error
                                   if self.title.isEmpty {
                                       self.title = "Error"
                                   }
                               }
                               else if self.title.isEmpty {
                                   self.title = "Some Place"
                               }
                           }
                       }
    }
}

上一篇
Day12 SwiftUI 05 - Life Cycle - SwiftUI App
下一篇
Day14 Combine 01 - 簡介
系列文
iOS 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言